package com.baseframe.chat.endpoint;


import cn.hutool.json.JSONUtil;
import com.baseframe.auth.entity.UserBean;
import com.baseframe.auth.utils.TokenUtil;
import com.baseframe.chat.entity.WebsocketMessage;
import com.baseframe.chat.service.SnsChatUserService;
import com.baseframe.tool.utils.spring.SpringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;


/**
 * websocket 端点 用于websocket会话
 */
@ServerEndpoint("/chat/message/{token}")
@SuppressWarnings("all")
@Slf4j
@Component
public class SnsChatEndpoint  {

    private final static ConcurrentHashMap<String,SnsChatEndpoint> CLIENT_ENDPOINT = new ConcurrentHashMap<>(16);


    private Session session;


    /**
     * 建立连接
     * @param session 会话
     * @param endpointConfig 端点配置
     * @param token 当前用户的令牌，因为为ws协议的原因，无法使用Authutil工具类获取用户信息，因此需要用其他方式去解析令牌获取用户信息
     */
    @OnOpen
    public void onOpen(Session session, EndpointConfig endpointConfig, @PathParam("token") String token) {
        this.session = session;

        UserBean userBean = TokenUtil.parse(token);
        //将发送消息的端点存储到缓存池中
        CLIENT_ENDPOINT.put(userBean.getUsername(),this);
        log.info("用户：{}，上线了",getUsernames());

    }

    public Set<String> getUsernames(){
        return CLIENT_ENDPOINT.keySet().stream().collect(Collectors.toSet());
    }

    /**
     * 接受客户端发送的消息
     * @param message
     * @param session
     */
    @OnMessage
    public void onMessage(String message,Session session){
        //转换Json消息为实例对象
        WebsocketMessage websocketMessage = JSONUtil.toBean(message, WebsocketMessage.class);
        log.info("原始消息体:{}",websocketMessage);
        String receiver = websocketMessage.getReceiver();

        /**
         * 因为spring在处理@ServerEndpoint注解的时候，在使用了该注解的类里面是不允许添加任何的依赖注入,因此这里使用SpringUtil.getBean从容器中获取所需要使用的bean
         */
        SnsChatUserService snsChatUserService = SpringUtil.getBean(SnsChatUserService.class);
        //发送数据
        SnsChatEndpoint snsChatEndpoint = CLIENT_ENDPOINT.get(receiver);
        if(snsChatEndpoint==null){
            //客户端不存在，将消息存到数据库
            boolean saveChatRecord = snsChatUserService.saveChatRecord(websocketMessage);
            if(!saveChatRecord){
                log.warn("【不在线的情况】用户：{}发送给用户：{}的消息：[{}]写入数据库失败",websocketMessage.getSender(),websocketMessage.getReceiver(),websocketMessage.getMessage());
            }
            return;
        }
        //客户端存在，将消息存到数据库
        boolean saveChatRecord = snsChatUserService.saveChatRecord(websocketMessage);
        if(!saveChatRecord){
            log.warn("【在线的情况】用户：{}发送给用户：{}的消息：[{}]写入数据库失败",websocketMessage.getSender(),websocketMessage.getReceiver(),websocketMessage.getMessage());
        }
        try {
            snsChatEndpoint.session.getBasicRemote().sendText(JSONUtil.toJsonStr(websocketMessage));

        } catch (IOException ex) {
            log.error("消息发送失败~");
            throw new RuntimeException(ex);
        }


    }

    @OnClose
    public void onClose(@PathParam("token") String token){
        // 用户下线
        UserBean userBean = TokenUtil.parse(token);
        CLIENT_ENDPOINT.remove(userBean.getUsername());
        log.info("用户：{}下线",userBean.getUsername());

    }
}
